﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.OpenGl;

namespace AvantGameMachine.Graphics
{
    public class Camera : Transformable
    {
        protected Box viewport = new Box(0, 0, 1024.0f, 768.0f);
        protected Box orthoBox = new Box(-1 * AGMPlatform.Device.ScreenW / 2, -1 * AGMPlatform.Device.ScreenH / 2,
            AGMPlatform.Device.ScreenW / 2, AGMPlatform.Device.ScreenH / 2);
        protected PosVector ViewDir = new PosVector(0.0f, 0.0f, -1.0f);
        protected PosVector RightVector = new PosVector (1.0f, 0.0f, 0.0f);
        protected PosVector UpVector = new PosVector(0.0f, 1.0f, 0.0f);
        protected PosVector	Rotated = new PosVector(0.0f,0.0f,0.0f);
        protected PosVector ViewPoint;
        protected float length = 10.0f;

        private RenderList children3D = new RenderList();
        private RenderList children2D = new RenderList();
        private bool transformed = true;

        public Camera()
        {
            position.Z = 10.0f;
            ViewPoint = Position + ViewDir;
        }

        public void RotateEyeX(float Angle)
        {
            Rotated.X += Angle;
	
    	    //Rotate viewdir around the right vector:
            ViewDir = (ViewDir * (float)Math.Cos(Angle) + UpVector * (float)Math.Sin(Angle)).Normalize();

        	//now compute the new UpVector (by cross product)
        	UpVector = ViewDir.CrossProduct(RightVector)*-1;
            transformed = true;
        }

        public void RotateEyeY(float Angle)
        {
	        Rotated.Y += Angle;
	
	        //Rotate viewdir around the up vector:
	        ViewDir = (ViewDir*(float)Math.Cos(Angle) - RightVector*(float)Math.Sin(Angle)).Normalize();


	        //now compute the new RightVector (by cross product)
	        RightVector = ViewDir.CrossProduct(UpVector);
            transformed = true;

        }

        public void RotateEyeZ(float Angle)
        {
	        Rotated.Z += Angle;
	
	        //Rotate viewdir around the right vector:
	        RightVector = (RightVector*(float)Math.Cos(Angle) + UpVector*(float)Math.Sin(Angle)).Normalize();

	        //now compute the new UpVector (by cross product)
	        UpVector = ViewDir.CrossProduct(RightVector)*-1;
            transformed = true;
        }

        public void Move(PosVector Direction)
        {
            Position = Position + Direction;
            transformed = true;
        }

        public void MoveBackward(float Distance)
        {
            Move(ViewDir*Distance);
        }

        public void MoveRight (float Distance)
        {
            Move(RightVector*Distance);
        }

        void MoveUpward(float Distance)
        {
            Move(UpVector*Distance);
        }

        public virtual void Update()
        {
            AGMPlatform.DVars.SetVar("CameraPos", position.ToString());
            AGMPlatform.DVars.SetVar("ViewDir", ViewDir.ToString());
            AGMPlatform.DVars.SetVar("ViewPoint", ViewPoint.ToString());
            AGMPlatform.DVars.SetVar("RightVector", RightVector.ToString());
            AGMPlatform.DVars.SetVar("UpVector", UpVector.ToString());
            AGMPlatform.DVars.SetVar("Rotated", Rotated.ToString());
        }

        internal void Reorder(IRenderizableObject _child,bool _is2d)
        {
            if (_is2d)
                children2D.Reorder(_child);
            else
                children3D.Reorder(_child);
        }

        internal Box Viewport
        {
            get { return viewport;  }
            set { viewport = value; }
        }

        internal Box OrthoBox
        {
            get { return orthoBox; }
            set { orthoBox = value; }
        }

        public float Left2D
        {
            get { return orthoBox.X; }
        }

        public float Right2D
        {
            get { return orthoBox.X1; }
        }

        public float Top2D
        {
            get { return orthoBox.Y1; }
        }

        public float Bottom2D
        {
            get { return orthoBox.Y; }
        }

        private void Push3DState()
        {
            PushViewAndProjection();
            SetModelviewIdentity();
            SetViewPort();
            SetProjectionIdentity();
            ViewPoint = Position + (ViewDir * length);

            Glu.gluPerspective(45, 1, 0.01f, 1000.0f);
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Glu.gluLookAt(position.X, position.Y, position.Z,
                        ViewPoint.X, ViewPoint.Y, ViewPoint.Z,
                        UpVector.X, UpVector.Y, UpVector.Z);
        }

        private void Push2DState()
        {
            PushViewAndProjection();
            SetProjectionIdentity();
            SetModelviewIdentity();
            SetViewPort();
            Gl.glOrtho(orthoBox.X, orthoBox.X1, orthoBox.Y, orthoBox.Y1, 1, -1);
        }

        private void Pop3DState()
        {
            PopViewAndProjection();
        }

        private void Pop2DState()
        {
            PopViewAndProjection();
        }

        private void PushViewAndProjection()
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glPushMatrix();
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glPushMatrix();
        }

        private void PopViewAndProjection()
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glPopMatrix();
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glPopMatrix();
        }

        private void SetProjectionIdentity()
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
        }

        private void SetModelviewIdentity()
        {
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
        }

        private void SetViewPort()
        {
            Gl.glViewport(viewport.XInt, viewport.YInt, viewport.X1Int, viewport.Y1Int);
        }

        public T CreateNew3DObject<T>(string _name) where T : IRenderizableObject,new()
        {
            T obj = new T();
            obj.Name = _name;
            obj.SetParentCamera(this,false);
            children3D.Add(obj);
            return obj;
        }

        public T CreateNew2DObject<T>(string _name) where T : IRenderizableObject, new()
        {
            T obj = new T();
            obj.Name = _name;
            obj.SetParentCamera(this,true);
            children2D.Add(obj);
            return obj;
        }

        internal void InternalRender()
        {
            PreRender();
            RenderChildren();
            PostRender();
        }

        public void RenderChildren()
        {
            if (children3D.Count > 0)
            {
                Push3DState();
                children3D.InternalRender();
                Pop3DState();
            }

            if (children2D.Count > 0)
            {
                Push2DState();
                children2D.InternalRender();
                Pop2DState();
            }
        }

        internal void NewMousePos(int _x, int _y)
        {
            // Translate the mouse pos to the 3D and 2D world and store it.

        }

        internal PosVector ConvertPointsToProjection(PosVector _pos, bool _is2D)
        {
            return new PosVector(ConvertPointsToProjectionX(_pos.X, _is2D), ConvertPointsToProjectionY(_pos.Y, _is2D), ConvertPointsToProjectionX(_pos.Z, _is2D));
        }

        internal float ConvertPointsToProjectionX(float _x, bool _is2D)
        {
            if (_is2D)
            {
                float x = (_x * orthoBox.XDistance / 10000.0f) + orthoBox.X;
                return x;
            }

            return _x;
        }

        public void AdjustViewportToTarget()
        {

        }

        internal float ConvertPointsToProjectionY(float _y, bool _is2D)
        {
            if (_is2D)
            {
                float y = (_y * orthoBox.YDistance / 10000.0f) - orthoBox.Y;
                return y;
            }

            return _y;
        }

        internal float ConvertPointsToProjectionZ(float _z, bool _is2D)
        {
            return _z;
        }

    }
}
